package main;

import builders.Factory;
import entities.PointObject;
import entities.TemporarySpring;
import force.Force;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.util.LinkedList;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.Timer;


/**
 * Creates an component that is a viewer onto an animation.
 * 
 * @author Robert C. Duvall
 */
@SuppressWarnings("serial")
public class Canvas extends JComponent {
    private static final int NO_KEY_PRESSED = -1;
    // animate 25 times per second if possible (in milliseconds)
    private static final int ONE_SECOND = 1000;
    private static final int FRAMES_PER_SECOND = 25;
    private static final int WALL_CHANGE_SIZE = 10;
    private static final JFileChooser OURCHOOSER = new JFileChooser(System
            .getProperties().getProperty("user.dir"));
    private static final double MOUSEPOINT_ID = -1;
    private static final double TEMPORARY_SPRING_CONSTANT = 0.005;
    private static final String MODEL_FILE_LOADER_PROMPT 
    = "Choose simulation file";
    private static final String ENVIRONMENT_FILE_LOADER_PROMPT
    = "Choose environment.xsp file (cancel to load no environments)";
    
    // user's game to be animated
    private Simulation myTarget;
    // drives simulation
    private Timer myTimer;
    // input state
    private int myLastKeyPressed;
    private Point myLastMousePosition;
    private Point myLastMousePressed;
    // only one so that it maintains user's preferences

    private Factory myFactory;
    private TemporarySpring myCreatableSpring;

    /**
     * Initializes the canvas.
     * 
     * @param size size of the canvas
     */
    public Canvas(Dimension size) {
        setSize(size);
        setPreferredSize(size);
        // set component to receive user input
        setInputListeners();
        setFocusable(true);
        requestFocus();
        // create timer to drive the animation--timer prompts actionPerformed
        myTimer = new Timer(ONE_SECOND / FRAMES_PER_SECOND,
                new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        step((double) FRAMES_PER_SECOND / ONE_SECOND);
                    }
                });
        // initialize simulation
        myTarget = new Simulation(this);
        myFactory = new Factory();
        load(ENVIRONMENT_FILE_LOADER_PROMPT);
        load(MODEL_FILE_LOADER_PROMPT);
    }

    /**
     * Starts the applet's action, i.e., starts the animation.
     */
    public void start() {
        myTimer.start();
    }

    /**
     * Take one step in the animation.
     * 
     * @param elapsedTime defines time elapsed of simulation
     */
    public void step(double elapsedTime) {
        myTarget.update(elapsedTime);
        // indirectly causes paint to be called
        repaint();
    }

    /**
     * Stops the applet's action, i.e., the animation.
     */
    public void stop() {
        myTimer.stop();
    }

    /**
     * Returns the last key pressed by the player (or -1 if none pressed).
     * 
     * @see java.awt.event.KeyEvent
     */
    public int getLastKeyPressed() {
        return myLastKeyPressed;
    }

    /**
     * Returns the last position of the mouse in the canvas.
     */
    public Point getLastMousePosition() {
        return myLastMousePosition;
    }

    public Point getLastMousePressed() {
        return myLastMousePressed;
    }

    /**
     * Paint the contents of the canvas.
     * 
     * Never called by you directly, instead called by Java runtime
     * when area of screen covered by this container needs to be
     * displayed (i.e., creation, uncovering, change in status)
     * 
     * @param pen used to paint shape on the screen
     */
    @Override
    public void paintComponent(Graphics pen) {
        pen.setColor(Color.WHITE);
        pen.fillRect(0, 0, getSize().width, getSize().height);
        myTarget.paint((Graphics2D) pen);
    }

    /**
     * Create listeners that will update state based on user input.
     */
    private void setInputListeners() {
        myLastKeyPressed = NO_KEY_PRESSED;
        addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                myLastKeyPressed = e.getKeyCode();
                manageKeySimulation(myLastKeyPressed);
            }

            @Override
            public void keyReleased(KeyEvent e) {
                myLastKeyPressed = NO_KEY_PRESSED;
            }
        });
        myLastMousePosition = new Point();
        addMouseMotionListener(new MouseMotionAdapter() {
            @Override
            // change from mouseMoved
            public void mouseDragged(MouseEvent e) {
                myLastMousePosition = e.getPoint();
            }

        });

        addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                myLastMousePressed = new Point(e.getPoint());
                manageMouseSimulation(myLastMousePressed);
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                myLastMousePressed = null;
                myTarget.removePointObject((int) MOUSEPOINT_ID);
                myTarget.removeUpdatable(myCreatableSpring);
                myTarget.removePaintable(myCreatableSpring);
            }
        });
    }

    private void load(String message) {
        OURCHOOSER.setDialogTitle(message);
        int response = OURCHOOSER.showOpenDialog(null);
        if (response == JFileChooser.APPROVE_OPTION) {
            myFactory.load(OURCHOOSER.getSelectedFile(), myTarget);
        }
    }

    private void manageMouseSimulation(Point myLastMousePressed) {
        LinkedList<Double> massProperties = new LinkedList<Double>();
        massProperties.add(myLastMousePressed.getX());
        massProperties.add(myLastMousePressed.getY());
        myFactory.createBuildable("mousemass", myTarget, massProperties);

        PointObject closestMass = (PointObject) myTarget
                .getClosestPointObject(myLastMousePressed);
        LinkedList<Double> springProperties = new LinkedList<Double>();
        springProperties.add(MOUSEPOINT_ID);
        springProperties.add((double) closestMass.getID());
        springProperties.add(Force.distanceBetween(myLastMousePressed,
                closestMass.getCenter()));
        springProperties.add(TEMPORARY_SPRING_CONSTANT);
        myFactory.createBuildable("tempspring", myTarget, springProperties);

    }

    private void manageKeySimulation(int keyCode) {
        switch (keyCode) {
            case KeyEvent.VK_SPACE:
                if (myTimer.isRunning()) {
                    stop();
                } 
                else {
                    start();
                }
                break;
            case KeyEvent.VK_S:
                step((double) FRAMES_PER_SECOND / ONE_SECOND);
                break;
            case KeyEvent.VK_P:
                System.out.println(myTarget);
                break;
            case KeyEvent.VK_N:
                load("Choose simulation file");
                break;
            case KeyEvent.VK_C:
                myTarget.reset();
                break;
            case KeyEvent.VK_G:
                myTarget.toggleForce("Gravity");
                break;
            case KeyEvent.VK_V:
                myTarget.toggleForce("Viscosity");
                break;
            case KeyEvent.VK_M:
                myTarget.toggleForce("CoM");
                break;
            case KeyEvent.VK_1:
                myTarget.toggleForce("Wall1");
                break;
            case KeyEvent.VK_2:
                myTarget.toggleForce("Wall2");
                break;
            case KeyEvent.VK_3:
                myTarget.toggleForce("Wall3");
                break;
            case KeyEvent.VK_4:
                myTarget.toggleForce("Wall4");
                break;
            case KeyEvent.VK_UP:
                myTarget.increaseSize(WALL_CHANGE_SIZE, WALL_CHANGE_SIZE);
                break;
            case KeyEvent.VK_DOWN:
                myTarget.decreaseSize(WALL_CHANGE_SIZE, WALL_CHANGE_SIZE);
                break;
            default:
                // good style
                break;
        }
    }
}
